﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SharpSoda.Message
{
    public class MessageDispatcher : IMessageDispatcher
    {
        protected int m_DefaultHeaderLength = 128;
        protected Dictionary<string, IMessageHandler> m_Handlers = new Dictionary<string, IMessageHandler>();

        // it is not thread safe ...
        public void RegisterHandler(IMessageHandler handler) 
        {
            string messageName = handler.GetMessageType().Name;
            if (m_Handlers.ContainsKey(messageName)) m_Handlers.Remove(messageName);
            m_Handlers.Add(messageName, handler);
        }

        // and this is not thread safe, too ... 
        // but "A Dictionary can support multiple readers concurrently, as long as the collection is not modified."
        public IMessageHandler GetHandler(string messageName)
        {
            IMessageHandler result = null;
            if (m_Handlers.TryGetValue(messageName, out result)) return result;
            else return null;
        }

        public virtual List<IMessageHandler> GetHandlers()
        {
            List<IMessageHandler> result = new List<IMessageHandler>();
            foreach (var item in m_Handlers) result.Add(item.Value);
            return result;
        }

        protected bool IsSubclassOfRawGeneric(Type toCheck, Type genericType)
        {
            while (toCheck != null && toCheck != typeof(object))
            {
                var currentType = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
                if (genericType == currentType)
                {
                    return true;
                }
                toCheck = toCheck.BaseType;
            }
            return false;
        }

        public virtual int LoadHandlers(string nameSpace = "", Assembly assembly = null)
        {
            Assembly targetAssembly = assembly;
            if (targetAssembly == null) targetAssembly = Assembly.GetEntryAssembly();

            if (targetAssembly != null)
            {
                var types = targetAssembly.GetTypes();
                foreach (var t in types)
                {
                    if ((nameSpace != null && nameSpace.Length > 0) ? t.Namespace == nameSpace : t.Namespace.Length > 0)
                    {
                        if (IsSubclassOfRawGeneric(t, typeof(MessageHandler<>)))
                        {
                            RegisterHandler((IMessageHandler)Activator.CreateInstance(t));
                        }
                    }
                }
            }

            return m_Handlers.Count;
        }

        protected virtual MessageSessionData GetSessionData(IMessageSession session, object data)
        {
            MessageSessionData result = null;
            if (data is string) result = new MessageSessionData(session, data.ToString());
            else if (data is byte[])
            {
                Byte[] bytes = data as byte[];
                int bodylen = bytes.Length - m_DefaultHeaderLength;
                if (bodylen >= 0)
                {
                    byte[] body = new byte[bodylen];
                    string message = Encoding.UTF8.GetString(bytes, 0, m_DefaultHeaderLength);
                    if (bodylen > 0) Buffer.BlockCopy(bytes, m_DefaultHeaderLength, body, 0, bodylen);
                    result = new MessageSessionData(session, message, body);
                }
            }
            return result;
        }

        protected virtual object ProcessMessage(MessageSessionData data)
        {
            if (data == null) return "Invalid message";
            string message = data.Message;
            if (message != null && message.Length > 0)
            {
                JsonMessage msg = JsonHelper.FromString<JsonMessage>(message);
                if (msg != null && msg.MessageName != null && msg.MessageName.Length > 0)
                {
                    IMessageHandler handler = GetHandler(msg.MessageName);
                    if (handler != null) return handler.Handle(data);
                }
            }
            return "Invalid message";
        }

        public virtual void Process(IMessageSession session, object data)
        {
            MessageSessionData sessionData = GetSessionData(session, data);

            //if (sessionData != null) ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessMessage), sessionData);
            if (sessionData != null) Task.Factory.StartNew(() => ProcessMessage(sessionData));
        }

        public virtual object Process(object data)
        {
            MessageSessionData sessionData = GetSessionData(null, data);
            if (sessionData != null) return ProcessMessage(sessionData);
            else return "Invalid message"; 
        }

    }
}
